package de.rs.espresso.problemprovider;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import de.rs.espresso.covers.DefaultCover;
import de.rs.espresso.cubes.CubeFactory;
import de.rs.espresso.interfaces.ICover;
import de.rs.espresso.interfaces.ICube;
import de.rs.espresso.interfaces.IGlobalInformation;
import de.rs.espresso.interfaces.IProblem;
import de.rs.espresso.interfaces.IProblemProvider;
import de.rs.espresso.interfaces.IResult;
import de.rs.espresso.problems.LogicalMinimizationProblem;
import de.rs.espresso.tools.Statistics;

/**
 * 
 * <p>
 * Provides a Parser for very simple BLIF files.
 * </p>
 * 
 * @author Jan
 * 
 */
public class BLIFFileExtractor implements IProblemProvider {

	File bFile;

	ArrayList<IProblem> problemList = new ArrayList<IProblem>();

	ArrayList<String> problemIDList = new ArrayList<String>();

	String fileContent;

	String modifiedContent;

	private int cubeCount = 0;

	private int cubeCountAfter = 0;

	@Override
	public void importFile(File bFile) throws IOException {
		this.bFile = bFile;

		FileReader fr = new FileReader(bFile);

		BufferedReader br = new BufferedReader(fr);

		StringBuilder sb = new StringBuilder();

		String line;

		while ((line = br.readLine()) != null) {

			sb.append(line + "\n");

		}

		fileContent = sb.toString();

		br.close();
	}

	@Override
	public void parse() throws ParseException {

		long id = Statistics.startMeasurement("PARSER");

		BufferedReader br = new BufferedReader(new StringReader(fileContent));
		String line;
		int lineCount = 0;
		boolean inNameBlock = false;
		boolean inFirstLine = false;
		StringBuilder sb = null;
		StringBuilder sbTempFileContent = new StringBuilder();
		try {
			while ((line = br.readLine()) != null) {
				lineCount++;
				inFirstLine = false;

				// Wenn bisher kein .names-Block verarbeitet wird, mit der
				// Verarbeitung beginnen.
				if (!inNameBlock && line.startsWith(".names")) {
					inNameBlock = true;
					inFirstLine = true;
					sb = new StringBuilder();
				}

				// Wenn gerade ein .names-Block verarbeitet wird, Zeile
				// analysieren ...
				if (inNameBlock) {

					// .names-Block Ende erreicht.
					if (line.startsWith(".") && !inFirstLine) {
						inNameBlock = false;
						String problemID = createProblem(sb.toString());
						sbTempFileContent.append(problemID);
					} else {
						sb.append(line + "\n");
					}

					if (line.startsWith(".names") && !inFirstLine) {
						inNameBlock = true;
						inFirstLine = true;
						sb = new StringBuilder();
						sb.append(line + "\n");
					}

				}

				if (!inNameBlock) {
					sbTempFileContent.append(line + "\n");
				}

			}

			if (inNameBlock) {
				String problemID = createProblem(sb.toString());
				sbTempFileContent.append(problemID);
			}

			modifiedContent = sbTempFileContent.toString();
			
			//System.out.println("Parsing done. Cubes: "+cubeCount);

		} catch (IOException e) {

			ParseException pe = new ParseException(
					"Unexpected ParseException \n" + e.getMessage(), lineCount);
			throw pe;
		} finally {
			Statistics.stopMeasurement(id);
		}

	}

	private String createProblem(String nameBlock) throws ParseException,
			IOException {

		String copy = new String(nameBlock);

		BufferedReader br = new BufferedReader(new StringReader(
				cleanUpFileContent(nameBlock)));
		String line;

		String nameLine = br.readLine();
		ArrayList<String> nameList = new ArrayList<String>(
				Arrays.asList(nameLine.split(" ")));

		ArrayList<ICube> cubeList = new ArrayList<ICube>();
		IGlobalInformation info = null;
		boolean first = true;
		while ((line = br.readLine()) != null) {
			if (first) {
				while((info = getGlobalInformation(line))==null) {
					line = br.readLine();
				}			
				first = false;
			}
			ICube cube = getCube(line, info);
			if(cube!=null) {
				cubeList.add(cube);
				cubeCount++;				
			}
		}

		if (cubeList.isEmpty()) {
			return copy;
		} else {

			ICover onSet = new DefaultCover(info);
			onSet.addAll(cubeList);

			LogicalMinimizationProblem mp = new LogicalMinimizationProblem(
					nameList, onSet, new DefaultCover(info), info);

			problemList.add(mp);
			problemIDList.add(mp.getIdentifier());

			return mp.getIdentifier();
		}

	}

	private IGlobalInformation getGlobalInformation(String line) {
		
		if(line.trim().isEmpty())
			return null;
		
		
		boolean outputPart = false;

		int inputs = 0;
		int outputs = 0;

		for (char literal : line.toCharArray()) {
			switch (literal) {
			case '1':
				if (!outputPart)
					inputs++;
				else
					outputs++;
				break;
			case '0':
				if (!outputPart)
					inputs++;
				else
					outputs++;
				break;
			case '-':
				if (!outputPart)
					inputs++;
				break;
			case ' ':
				outputPart = true;
				break;
			default:
				System.err.println("Parse Error");
				System.exit(0);
			}
		}

		return new Info(inputs, outputs);
	}

	private ICube getCube(String line, IGlobalInformation info) {
		
		if(line.trim().isEmpty())
			return null;
		
		boolean outputPart = false;

		List<Byte> inputs = new ArrayList<Byte>();
		List<Byte> outputs = new ArrayList<Byte>();

		for (char literal : line.toCharArray()) {
			switch (literal) {
			case '1':
//				if (!outputPart)
					inputs.add(ICube.DIRECT);
//				else
//					outputs.add(ICube.OUTPUT_PRESENT);
				break;
			case '0':
//				if (!outputPart)
					inputs.add(ICube.NEGATED);
//				else
//					outputs.add(ICube.OUTPUT_NOT_PRESENT);
				break;
			case '-':
//				if (!outputPart)
					inputs.add(ICube.DONT_CARE);
				break;
			case ' ':
				//outputPart = true;
				break;
			default:
				System.err.println("Parse Error");
				System.exit(0);
			}
		}
		
		outputs.add(inputs.get(inputs.size()-1));
		inputs.remove(inputs.size()-1);

		return CubeFactory.getCube(inputs, outputs, info);
	}

	private String getString(List<ICube> cubes) {

		StringBuilder sbCoverString = new StringBuilder();

		for (ICube cube : cubes) {
			String cubeLine = cube.getBlifString();
			if (cubeLine != null) {
				cubeCountAfter  ++;
				sbCoverString.append(cubeLine);
				sbCoverString.append("\n");
			}
		}
		return sbCoverString.toString();

	}

	/*
	 * removes comments and \ - line breaks from fileContent
	 */
	private String cleanUpFileContent(String s) throws ParseException {
		String cleanString = new String(s);

		if (cleanString.length() <= 0)
			return cleanString;

		// Remove all comments
		cleanString = cleanString.replaceAll("#.* \n", " ");
		// Remove all line breaks
		cleanString = cleanString.replaceAll("\\\\\n", " ");
		
		
		// If there are still line breaks they are not used as intended
		if (cleanString.matches("\\\\"))
			throw new ParseException("Illegal use of \\", 0);

		return cleanString;
	}

	@Override
	public List<IProblem> getProblems() {
		return problemList;
	}

	private class Info implements IGlobalInformation {
		private int inputs;
		private int outputs;

		public Info(int inputs, int outputs) {
			this.inputs = inputs;
			this.outputs = outputs;
		}

		@Override
		public int getNumberOfInputs() {
			return inputs;
		}

		@Override
		public int getNumberOfOutputs() {
			return outputs;
		}
	}

	public void writeBack(List<IResult> results) {

		long measurementId = Statistics.startMeasurement("WRITE_BACK");

		HashMap<String, LogicalMinimizationProblem> resultMap = new HashMap<String, LogicalMinimizationProblem>();

		for (IResult result : results) {
			if (result instanceof LogicalMinimizationProblem) {
				LogicalMinimizationProblem lmp = (LogicalMinimizationProblem) result;
				resultMap.put(lmp.getIdentifier(), lmp);
			}
		}

		StringBuilder sb = new StringBuilder();

		LogicalMinimizationProblem result;
		
		int begin;
		int end = 0;
		while((begin = modifiedContent.indexOf("$$$problem_id_",end))!=-1) {
			
			sb.append(modifiedContent.substring(end, begin));
			
			
			end = modifiedContent.indexOf("$$$", begin+14);
			String problemID = modifiedContent.substring(begin, end+3);
		
			if (resultMap.containsKey(problemID)) {
				result = resultMap.get(problemID);
				sb.append(getNames(result.getNames()));
				sb.append(getString(result.getCover()));
			}
			
			end += 3;
		}
		
		sb.append(modifiedContent.substring(end));
		
		

		FileWriter fw;

		File f;

		try {
			f = new File(bFile.getAbsolutePath().replace(".blif",
					"_minimized.blif"));
			fw = new FileWriter(f, false);

			fw.write(sb.toString());
			fw.flush();
			fw.close();
			
			//System.out.println(cubeCount);
			
			System.out.println("Finished: "+ String.format("%-"+50+"s",bFile.getAbsolutePath() ) + "  | Cubes: " + String.format("%"+6+"s",cubeCount)  + " -> "+ String.format("%-"+6+"s",cubeCountAfter)  + " |  "+  String.format("%"+6+"s",(cubeCountAfter-cubeCount)) );

		} catch (IOException e) {
			e.printStackTrace();
		}

		Statistics.stopMeasurement(measurementId);

	}

	private String getNames(List<String> names) {
		StringBuilder sb = new StringBuilder();
		// sb.append(".names");
		for (String name : names) {
			sb.append(name);
			sb.append(" ");
		}

		sb.append("\n");

		return sb.toString();
	}

}
